package apibasej.log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.SimpleFormatter;

import apibasej.basic.config.ConfigConsumerInterface;
import apibasej.basic.config.ConfigManager;
import apibasej.basic.config.Prop;
import apibasej.basic.exception.APIBaseException;
/**
 * http://code.google.com/p/apibasej/
 * 
 * @author Julio Azevedo Lessa - apibasej@gmail.com
 * 
 */
public class MySimpleFileHandler extends Handler implements ConfigConsumerInterface{

	private FileOutputStream fos;
	private File file;
	
	private File dir;
	private String iniFile;
	private String endFile;
	
	
	private Integer maxFiles = 2000;
	private Integer maxFileSizeInBytes = 5*1024*1024; // 5 MB
	
	private ArrayList<String> filesToDel = new ArrayList<String>();
	private ArrayList<String[]> filesToRename = new ArrayList<String[]>();

	private ConfigManager conf = new ConfigManager();
	@Override
	public <T> T getProp(Prop<T> prop) {
		return conf.getProp(prop);
	}
	
	
	public static void main(String[] args) throws APIBaseException, InterruptedException {
		MySimpleFileHandler fh = new MySimpleFileHandler("D:/testlog/testl.txt");
		//fh.setMaxFiles(9);
		//fh.setMaxFileSizeInBytes(1000);
		MySimpleLogFormater lf = new MySimpleLogFormater();
		fh.setFormatter(lf);
		for(int i=0; i<1; i++){
			LogRecord lr = new LogRecord(Level.ALL, "xxxxxXXXaaaaaaXXXXXXXXXXXXxxxxx"+Math.random());
			lr.setThrown(new Exception("EEEEEEEEEEE"));
			fh.publish(lr);
			fh.publish(new LogRecord(Level.ALL, "xxxxxXeeeeeeXXXXxxxxx"+Math.random()));
			fh.publish(new LogRecord(Level.ALL, "xxxwwwwwwwwwwwwwwwwXXxxxxx"+Math.random()));
			//Thread.sleep(5);
		}
		fh.close();
	}
	
	
	
	// '?' será substiuído pelo sequencial. EX: C:/logs/MYLOG?.log
	// se não tiver será adicionado antes do último ponto
	public MySimpleFileHandler(String filePattern) throws APIBaseException {
		int posLastBar = filePattern.lastIndexOf("/");
		if(posLastBar<0) posLastBar = filePattern.lastIndexOf("\\");
		String dirFileLogger = posLastBar<0?"/":filePattern.substring(0,posLastBar+1);
		this.dir = new File(dirFileLogger);
		this.dir.mkdirs();
		
		int posSeq = filePattern.indexOf('?');
		int jump = 1;
		if(posSeq<0){
			posSeq = filePattern.lastIndexOf('.');
			jump = 0;
			if(posSeq<0) throw new APIBaseException("The file pattern must contain '.' or '?'");
		}
		if(posLastBar>=0) iniFile = filePattern.substring(posLastBar+1,posSeq);
		else iniFile = filePattern.substring(0,posSeq);
		endFile = filePattern.substring(posSeq+jump);
		
	}
	
	@Override
	public Formatter getFormatter() {
		if(super.getFormatter()==null) setFormatter(new SimpleFormatter());
		return super.getFormatter();
	}
	
	private final int getNextSeqFile(){
		delLockeds();
		renameLockeds();
		ArrayList<Integer> seqs = new ArrayList<Integer>();
		l1: for(String f : dir.list()){
			if(f.startsWith(iniFile) && f.endsWith(endFile)){
				String numSeq = f.substring(iniFile.length(),f.length()-endFile.length());
				for(char c : numSeq.toCharArray()) {
					if( !Character.isDigit(c)) continue l1;
				}
				seqs.add(Integer.parseInt(numSeq));
			}
		}
		int qtSeqs = seqs.size();
		if(qtSeqs>0){
			Collections.sort(seqs);
			int qtDel = qtSeqs - maxFiles+1; // +1 porque vai criar 1 depois
			for(int i=0;i<qtDel;i++) delFile( getFileName(seqs.get(i)) );// apaga os menores seqs
			
			int minSeq = seqs.get(0);
			if(minSeq>=maxFiles*5){// *5 porque o nome do arquivo já tem um 0 de folga, ou seja, os nómeros podem ser até 10 vezes maior que a atd max
				return renameAllFiles(seqs);
				
			}else{
				int maxSeq = seqs.get(qtSeqs-1);
				return maxSeq+1;
			}
		}else{
			return 1;
		}
	}
	
	
	private final Integer renameAllFiles(ArrayList<Integer> seqs){// ajusta os seqs de todos arquivos na ordem
		try{
			if(fos!=null) fos.close();
		}catch (Throwable e) {
			e.printStackTrace();
		}
		fos = null;
		int newSeq = 1;
		for(Integer s : seqs){
			renameFile(new String[]{getFileName(s),getFileName(newSeq)});
			newSeq++;
		}
		return newSeq;
	}
	
	
	private final String getFileName(Integer seq){
		String s = seq.toString();
		int qtCharsSeqFile = maxFiles.toString().length()+1;// mais 1 para ter uma casa de folga para deletar arquivos antigos
		while(s.length()<qtCharsSeqFile) s = '0'+s;
		return iniFile+s+endFile;
	}
	
	
	// apaga os arquivos que não foi possível deletar antes
	private final void delLockeds(){
		if(!filesToDel.isEmpty()){
			ArrayList<String> delsOK = new ArrayList<String>();
			for(String path : filesToDel) {
				try{
					if(del(path)) delsOK.add(path);
				}catch (Throwable e) {} // se não conseguir tenta depois
			}
			for(String pathDeleted : delsOK) filesToDel.remove(pathDeleted);
		}
	}
	// renomeia os arquivos que não foi possível renomear antes
	private final void renameLockeds(){
		if(!filesToRename.isEmpty()){
			ArrayList<String[]> renOK = new ArrayList<String[]>();
			for(String[] paths : filesToRename) {
				try{
					if(ren(paths)) renOK.add(paths);
				}catch (Throwable e) {} // se não conseguir tenta depois
			}
			for(String[] pathRen : renOK) filesToRename.remove(pathRen);
		}
	}
	// tenta apagar o arquivo, se não conseguir adiciona na lista para apagar depois
	private final void delFile(String pathDel){
		try{
			if(!del(pathDel)) filesToDel.add(pathDel);
		}catch (Throwable e) {// se não conseguir tenta depois
			filesToDel.add(pathDel);
		} 
	}
	// tenta renomear o arquivo, se não conseguir adiciona na lista para renomear depois
	private final void renameFile(String[] paths){
		try{
			if(!ren(paths)) filesToRename.add(paths);
		}catch (Throwable e) {// se não conseguir tenta depois
			filesToRename.add(paths);
		} 
	}
	private final boolean del(String pathDel){
		File f = new File(this.dir,pathDel);
		if(!f.exists()) return true;
		return f.delete();
	}
	private final boolean ren(String[] paths){
		File f = new File(this.dir,paths[0]);
		if(!f.exists()) return true;
		return f.renameTo(new File(this.dir,paths[1]));
	}
	

	
	
	@Override
	public void close() throws SecurityException {
		try {
			if(fos!=null){
				flush();
				fos.close();
				fos = null;
			}
			delLockeds();
			for(String delFail : filesToDel) {
				System.out.println(MySimpleFileHandler.class.getName()+": Error on delete file: "+delFail); 
			}
			renameLockeds();
			for(String[] renFail : filesToRename) {
				System.out.println(MySimpleFileHandler.class.getName()+": Error on rename file: "+renFail[0]+"; new name: "+renFail[1]); 
			}
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}

	@Override
	public void flush() {
		try {
			if(fos!=null)fos.flush();
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}

	private final void newFileOut(){
		if(fos!=null){
			try{
				fos.close();
			}catch (Throwable e) {
				e.printStackTrace();
			}
		}
		fos = null;
		for(int i=0;i<3;i++){
			try{
				String path = getFileName(getNextSeqFile());
				this.file = new File(this.dir,path);
				this.fos = new FileOutputStream( this.file, true);
				return;// se ok não tenta denovo
			}catch (Throwable e) {
				e.printStackTrace();
			}
		}
	}
	
	@Override
	public void publish(LogRecord record) {
		try{
			if(fos==null || file.length()>=maxFileSizeInBytes) newFileOut();
			write(record);
			
		}catch (Throwable e) {
			
			e.printStackTrace();
			try {
				newFileOut();
				write(record);
			} catch (Throwable e1) {
				e1.printStackTrace();
				fos = null;
			}
		}
	}

	private final void write(LogRecord record) throws IOException{
		fos.write(getFormatter().format(record).getBytes());
	}


	public Integer getMaxFiles() {
		return maxFiles;
	}
	public void setMaxFiles(Integer maxFiles) {
		this.maxFiles = maxFiles;
	}
	public Integer getMaxFileSizeInBytes() {
		return maxFileSizeInBytes;
	}
	public void setMaxFileSizeInBytes(Integer maxFileSizeInBytes) {
		this.maxFileSizeInBytes = maxFileSizeInBytes;
	}

	
}
